/*
 * MIT License
 * 
 * Copyright (c) 2017 wen.gu <454727014@qq.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: win_thread.c

 *
 * Purpose: windows thread implement
 *
 * Developer:
 *   wen.gu , 2014-06-08
 *
 * TODO: 
 *
 ***************************************************************************/

//inner header file
#include "sys_config.h"


#if defined(USE_WIN32_THREAD_API) && USE_WIN32_THREAD_API//API header file 

#include "vos_thread.h"
#include "vos_condition.h"
#include "vos_mutex.h"
#include "general_macro.h"


#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <winbase.h>
#include <process.h>
#include <stdio.h>

#include <map>
using namespace std;

#pragma comment(lib, "winmm.lib")

#include "vos_mem.h"
#define THD_PRINT printf
/***************************************************************************
 *
 * thread information define
 *
 ***************************************************************************/

/***************************************************************************
 *
 * mutex lock API define
 *
 ***************************************************************************/
/**
*@brief  get the size of mutex instance needed memory(for inter-process communication)
*
*@param  None.
*
*@retuen if success return mutex intstance size, else return 0.
*@see
*/
G_API GU32 vosGetMutexInstanceSize()
{
    return sizeof (HANDLE);
}

/**
*@brief  initialize a mutex lock with external buffer
*
*@param  exBuf [in] external buffer for mutex instance.
*@param  size  [in] the size of external buffer.
*
*@retuen if success return mutex intstance, else return 'NULL'.
*@see
*/
G_API GPHD vosMutexInit(GS08* extBuf, GU32 size);

/**
*@brief  uninitialize a mutex lock instance
*
*@param  mutex [in] mutex instance handle.
*
*@retuen None.
*@see
*/
G_API void vosMutexUninit(GPHD mutex);

/**
*@brief  create a mutex lock instance
*
*@param  None.
*
*@retuen if success return mutex lock intstance, else return 'NULL'.
*@see 
*/
GPHD vosMutexCreate(void)
{
    return (GPHD)CreateMutex(NULL, FALSE, NULL);
}

/**
*@brief  free a mutex lock instance
*
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen None.
*@see 
*/
void vosMutexFree(GPHD mutex)
{
    CloseHandle((HANDLE)mutex);
}

/**
*@brief  lock on
*
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen None.
*@see 
*/
void vosMutexLock(GPHD mutex)
{
    WaitForSingleObject((HANDLE)mutex, INFINITE);
}

/**
*@brief  lock off
*
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen None.
*@see 
*/
void vosMutexUnlock(GPHD mutex)
{
    ReleaseMutex((HANDLE)mutex);
}

/***************************************************************************
 *
 * condition lock API define
 *
 ***************************************************************************/
/**
*@brief  create a condition lock instance
*
*@param  None.
*
*@retuen if success return condition lock intstance, else return 'NULL'.
*@see 
*/
GPHD vosCondCreate(void)
{
    return (GPHD)CreateEvent(NULL, TRUE, FALSE, NULL);
}

/**
*@brief  free a condition lock instance
*
*@param  cond [in] a condition lock instance handle.
*
*@retuen None.
*@see 
*/
void vosCondFree(GPHD cond)
{
    CloseHandle(cond);
}

/**
*@brief  condition wait a signal.
*
*@param  cond  [in] a condition lock instance handle.
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen if success return G_OK,else return error code.
*@see 
*/
GS32 vosCondWait(GPHD cond, GPHD mutex)
{
    ResetEvent(cond); //reset event
    ReleaseMutex(mutex); //release external mutex lock
    //waitting event hanppen(like wait condition variable)
    WaitForSingleObject(cond, INFINITE); 
    WaitForSingleObject(mutex, INFINITE); //lock on external mutex lock

    return G_OK; //todo fix me
}

/**
*@brief  condition wait a signal with timeout
*
*@param  cond  [in] a condition lock instance handle.
*@param  mutex [in] a mutex lock instance handle.
*@param  timeOut [in] wait timeout time,unit in milli second(ms).
*
*@retuen if success return G_OK,else return error code.
*@see 
*/
GS32 vosCondTimedWait(GPHD cond, GPHD mutex, GU32 timeOut)
{
    GS32 ret = G_OK;
    ResetEvent(cond); //reset event
    ReleaseMutex(mutex); //release external mutex lock
    //waitting event hanppen(like wait condition variable)

    if (WaitForSingleObject(cond, timeOut) != WAIT_OBJECT_0)
    {
        ret = G_ErrorTimeout;
    }

    WaitForSingleObject(mutex, INFINITE); //lock on external mutex lock

    return ret; //todo fix me
}

/**
*@brief  condition send a signal 
*
*@param  cond [in] a condition lock instance handle.
*
*@retuen None.
*@see 
*/
void vosCondSignal(GPHD cond)
{
    SetEvent(cond); //todo fix me
}

/**
*@brief  condition send broadcast signal 
*
*@param  cond [in] a condition lock instance handle.
*
*@retuen None.
*@see 
*/
void vosCondBroadcast(GPHD cond)
{
    SetEvent(cond); //todo fix me.
}

/***************************************************************************
 *
 * thread API define
 *
 ***************************************************************************/
typedef struct winThread_s
{
    GU32 tid; //thread id
    uintptr_t thd;
    void* param;  //thread loop function's parameter
    vosThreadFunc_t loopFunc;
    vos_thread_attr_t attr;
    GS08 name[THD_NAME_MAX_LEN + 1];
}winThread_t;

class myTinyLocker
{
public:
    inline myTinyLocker() { m_hMutex = ::CreateMutex(NULL, FALSE, NULL); }
    inline ~myTinyLocker() { ::CloseHandle(m_hMutex); }
    inline void lock() const { ::WaitForSingleObject(m_hMutex, INFINITE); }
    inline void unlock() const { ::ReleaseMutex(m_hMutex); }
private:
    HANDLE m_hMutex;
};

class myTinyAutoLock
{
public:
    myTinyAutoLock(const myTinyLocker& lock):mLock(lock)
    {
        mLock.lock();
    }

    ~myTinyAutoLock()
    {
        mLock.unlock();
    }
private:
    const myTinyLocker& mLock;
};


struct myThdRegItem
{
public:
    HANDLE thd;
    GU32 tid;
    GBOL joinable;
};

class myThreadHandleReg
{
private:
    ~myThreadHandleReg()
    {
        GS32 ret = 0;
        map<GU32, myThdRegItem>::const_iterator it = mReg.begin();

        for (; it != mReg.end(); it++)
        {
            myThdRegItem* item = (myThdRegItem*)&(it->second);
            CloseHandle(item->thd);
            //ret = GetLastError();
        }

        mReg.clear();
    }
public:
    static myThreadHandleReg* getInstance()
    {
        myTinyAutoLock autoLock(mLock);
        static myThreadHandleReg gThdReg;

        return &gThdReg;
    }

    void insert(GU32 key, myThdRegItem item)
    {
        myTinyAutoLock autoLock(mLock);
        mReg.insert(make_pair(key, item));
    }

    myThdRegItem* find(GU32 key)
    {
        myTinyAutoLock autoLock(mLock);
        map<GU32, myThdRegItem>::const_iterator it = mReg.find(key);

        if (it == mReg.end()) {

            return NULL;
        }

        return (myThdRegItem*)&(it->second);
    }

    GS32 destroyItem(GU32 key)
    {
        myTinyAutoLock autoLock(mLock);
        map<GU32, myThdRegItem>::const_iterator it = mReg.find(key);

        if (it == mReg.end())
        {
            return G_FAIL;
        }

        myThdRegItem* item = (myThdRegItem*)&(it->second);

        CloseHandle(item->thd);
        mReg.erase(it);

        return G_OK;

    }
private:
    static myTinyLocker mLock;

    map<GU32, myThdRegItem> mReg;
};

//static
myTinyLocker myThreadHandleReg::mLock;
//////////////////////////////////////////////////////////////////////////
/**
*@brief  thread loop function type
*
*@param  param [in] external handle
*
*@retuen if success exit return G_OK , else return error code.
*@see 
*/
static unsigned int __stdcall threadFunc(void* param) 
{
    winThread_t* winThd = (winThread_t*)param;
    vos_thread_attr_t* attr = &(winThd->attr);
    THD_PRINT("start thread(%s) loop\n", winThd->name);
    GS32 ret = winThd->loopFunc(winThd->param);
    THD_PRINT("thread(%s) exit with ret(%d)\n", winThd->name, ret);

    
    if (attr->joinable == GFALSE) //detach mode
    {
        myThreadHandleReg* thdReg = myThreadHandleReg::getInstance();

        thdReg->destroyItem(winThd->tid);
        winThd->thd = 0;
        memFree(winThd);   
    }
    //_endthreadex(0); //todo modify return value(default: 0)
    return ret;
}

/**
*@brief  create a thread instance
*
*@param  threadId  [io] thread id output
*@param  loopFunc  [in] thread main loop function.
*@param  loopParam [in] thread loop function's parameter.
*@param  attr      [in] thread attribute.
*
*@retuen if success return G_OK, else return G_FAIL.
*@see
*@note   thread default state(is param attr is NULL) is joinable(mean that, use
*        need call function vosThreadjoin to release thread resource)
*
*/
GS32 vosThreadCreate(vos_thread_id *threadId, vosThreadFunc_t loopFunc, void* loopParam, vos_thread_attr_t* attr)
{
    winThread_t* winThd = NULL;
    GU32 tid = 0;
    myThdRegItem item;
    myThreadHandleReg* thdReg = myThreadHandleReg::getInstance();
    GCHECK(threadId);
    *threadId = VTHD_INVALID_TID;
    winThd = (winThread_t*)memAlloc(sizeof(winThread_t));
    

    if (!winThd)
    {
        printf("allocate win thread instance failed\n");
        goto EXIT;
    }

    memset(winThd, 0, sizeof(winThread_t));

    if (attr &&  attr->name)
    {
        GS32 len = strlen((const char*)attr->name);

        if (len > THD_NAME_MAX_LEN)
        {
            printf("thread name too long\n");
            memcpy(winThd->name, attr->name, THD_NAME_MAX_LEN);
        }
        else
        {
            memcpy(winThd->name, attr->name, len);
        }
    }
    else
    {        
        memcpy(winThd->name, "unknown", 6);
    }
    winThd->loopFunc = loopFunc;   
    winThd->param    = loopParam;
    winThd->thd      = _beginthreadex(NULL, 0, threadFunc, winThd, CREATE_SUSPENDED, &tid); // or CREATE_SUSPENDED(start with pause state), 0(with run state)

    if (!winThd->thd)
    {
        memFree(winThd);
        winThd = NULL;
        goto EXIT;
    }

    *threadId = reinterpret_cast<vos_thread_id>(tid); //create success, here output thread instance handle.   
    item.tid  = tid;
    item.thd  = (HANDLE)winThd->thd;    

    if (attr)
    {
        winThd->attr = *attr;
        item.joinable = attr->joinable;
    }
    else
    {
        item.joinable = GFALSE;
    }

    thdReg->insert(tid, item);

    winThd->tid = tid;
    ResumeThread((HANDLE)winThd->thd); //here resum thread
    
EXIT:

    if (!winThd)
    {
        return G_FAIL;
    }

    return G_OK;
}

/**
*@brief  wait for thread to release resource
*
*@param  threadId [in] the id of thread.
*
*@retuen if sucess return G_OK(0),else return error code
*@see
*@note  if thread not in joinable state,then will be return 'G_ErrorInvalidOperation'
*/
GS32 vosThreadJoin(vos_thread_id threadId)
{
    if (threadId != VTHD_INVALID_TID)
    {
        GU32 tid = (GU32)threadId;

        if (threadId != vosThreadGetId())
        {
            myThreadHandleReg* thdReg = myThreadHandleReg::getInstance();
            myThdRegItem* item = thdReg->find(tid);

            if (item)
            {
                if (item->joinable == GTRUE)
                {
                    DWORD ret = 0;
                    WaitForSingleObject(item->thd, INFINITE);
                    GetExitCodeThread(item->thd, &ret);
                    thdReg->destroyItem(tid);
                    return G_OK; //todo process return value.
                }
                else
                {
                    return G_ErrorInvalidOperation;
                }
            }
        }
        else
        {
            printf("can not call(%s) in thread self\n", __FUNCTION__);
            return G_ErrorWouldBlock;
        }
    }

    return G_FAIL;
}


/**
*@brief  get thread ID
*
*@param
*
*@retuen return thread ID
*@see
*/
vos_thread_id vosThreadGetId(void)
{
    return reinterpret_cast<vos_thread_id>(GetCurrentThreadId());
}

#endif //end of USE_WIN32_THREAD_API
